package uk.co.davedexter.flickruploader;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.parsers.ParserConfigurationException;

import org.xml.sax.SAXException;

import com.aetrion.flickr.Flickr;
import com.aetrion.flickr.FlickrException;
import com.aetrion.flickr.REST;
import com.aetrion.flickr.RequestContext;
import com.aetrion.flickr.auth.Auth;
import com.aetrion.flickr.auth.AuthInterface;
import com.aetrion.flickr.auth.Permission;
import com.aetrion.flickr.photos.Photo;
import com.aetrion.flickr.photos.PhotoList;
import com.aetrion.flickr.photos.PhotosInterface;
import com.aetrion.flickr.photos.SearchParameters;
import com.aetrion.flickr.uploader.UploadMetaData;
import com.aetrion.flickr.uploader.Uploader;
import com.aetrion.flickr.util.FileAuthStore;

public class FlickrUploader {

	// Current top count of images. Needs updating once more uploads have taken
	// place.
	private static int topCount = 20269;

	private File topLevelPhotoDir;
	private int id;
	private HashMap<File, String> images;
	private Connection conn;
	private Statement s;
	private ResultSet results;
	private PreparedStatement ps;
	private int count;

	private Flickr flickr;
	private FileAuthStore store;
	AuthInterface authInterface;
	Auth auth;
	private static String apiKey = "69d692ab74bf18622850333aa14e4ee0";
	private static String secret = "ab48698ad2813b7e";
	private Uploader up;

	private ArrayList<File> notUploaded;
	private ArrayList<File> cannotDistinguish;
	private ArrayList<File> alreadyUploaded;
	private long start;

	FlickrUploader(String topLevel) {
		start = System.currentTimeMillis();

		// Set up the arraylists for tracking the photos
		notUploaded = new ArrayList<File>();
		cannotDistinguish = new ArrayList<File>();
		alreadyUploaded = new ArrayList<File>();

		// Check the top level directory
		System.out.println("Checking the directory exists.");
		topLevelPhotoDir = new File(topLevel);
		if ((!topLevelPhotoDir.exists()) | (!topLevelPhotoDir.isDirectory())) {
			System.err.println("The directory does not exist: " + topLevelPhotoDir);
			System.exit(-1);
		}

		// Check the database
		try {
			// load the embedded driver
			Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance();
			conn = DriverManager.getConnection("jdbc:derby:Photos");

		} catch (SQLException se) {
			String cause = se.getCause().toString();
			if (cause.contains("Database 'Photos' not found.")) {
				System.out.println("Appears to be the first time run. Creating database - please wait.");
				try {
					conn = DriverManager.getConnection("jdbc:derby:Photos;create=true");
					s = conn.createStatement();
					s.execute("create table photos(id int, file varchar(255), tags varchar(255), uploaded varchar(5), search_count int)");
				} catch (SQLException se1) {
					se1.printStackTrace();
				}
				cleanUp();
				System.out.println("Please run again.");

				System.exit(0);

			} else {
				se.printStackTrace();
				cleanUp();
			}
		} catch (InstantiationException ie) {
			ie.printStackTrace();
			cleanUp();
		} catch (IllegalAccessException iae) {
			iae.printStackTrace();
			cleanUp();
		} catch (ClassNotFoundException cnfe) {
			cnfe.printStackTrace();
			cleanUp();
		}

		// Set up the flickr connection
		try {
			flickr = new Flickr(apiKey, secret, new REST());
			authInterface = flickr.getAuthInterface();

			RequestContext rc = RequestContext.getRequestContext();
			up = new Uploader(apiKey, secret);

			File authDir = new File("authstore");
			if (authDir.exists() & authDir.isDirectory()) {
				System.out.println("Loading existing authorisation.");
				store = new FileAuthStore(authDir);
				auth = store.retrieve("47213205@N04");
				rc.setAuth(auth);
			} else {
				// Prompt the user.
				String frob = authInterface.getFrob();
				URL url = authInterface.buildAuthenticationUrl(Permission.WRITE, frob);
				System.out.println("Press return after you granted access at this URL:");
				System.out.println(url.toExternalForm());
				BufferedReader infile = new BufferedReader(new InputStreamReader(System.in));
				infile.readLine();
				auth = authInterface.getToken(frob);

				rc.setAuth(auth);

				store = new FileAuthStore(new File("authstore"));
				store.store(auth);
			}
		} catch (IOException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (FlickrException e) {
			e.printStackTrace();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		}

		// Read the images
		System.out.println("Locating the images on disk.");
		images = new HashMap<File, String>();
		try {
//			System.out.println("Deleting Anna and Wes from db");
//			s = conn.createStatement();
//			s.execute("delete from photos where file like '%Anna and Wes%'");
////			s.execute("delete from photos where file like '%cr2'");
//	        System.exit(0);
			
			System.out.println("Getting the current highest id.");
			id = getHighestID();
			System.out.println("Highest current ID in database is: " + id);
			id += 1;
			System.out.println("Loading details from the database. Please wait.");
			loadImagesFromDB();
			count = 0;
			readPhotoFiles(topLevelPhotoDir);
			System.out.println("Completed read of files in " + topLevelPhotoDir);
			System.out.println("Images to process: " + images.size());
			System.out.println("Time running: " + (System.currentTimeMillis() - start) / 1000 + "s");
		} catch (SQLException se) {
			se.printStackTrace();
		}
	}

	private int getHighestID() throws SQLException {
		s = conn.createStatement();
		s.setMaxRows(1);
		results = s.executeQuery("SELECT id FROM photos WHERE id IS NOT NULL ORDER BY id DESC");
		if (results.next()) {
			return (results.getInt("id"));
		} else {
			return (topCount);
		}
	}

	private void loadImagesFromDB() throws SQLException {
		s = conn.createStatement();
		results = s.executeQuery("SELECT file,tags,uploaded,search_count FROM photos ORDER BY id");
		while (results.next()) {
			images.put(new File(results.getString("file")), results.getString("tags"));
			if ((results.getString("uploaded")) != null && (results.getString("uploaded").equals("true"))) {
				alreadyUploaded.add(new File(results.getString("file")));
			}
			if (results.getInt("search_count") > 1) {
				cannotDistinguish.add(new File(results.getString("file")));
			}
			if (results.getInt("search_count") == 0) {
				notUploaded.add(new File(results.getString("file")));
			}
		}
	}

	private void readPhotoFiles(File dir) throws SQLException {
		ps = conn.prepareStatement("INSERT INTO photos (file, tags) VALUES (?, ?)");

		if (dir.isDirectory()) {
			String[] children = dir.list();
			for (int i = 0; i < children.length; i++) {
				readPhotoFiles(new File(dir, children[i]));
			}
		} else {
			if (!images.keySet().contains(dir)) {
				String filename = dir.getName();
				String ext = (filename.lastIndexOf(".") == -1) ? "" : filename.substring(filename.lastIndexOf(".") + 1,
						filename.length());
				if (ext.toLowerCase().equals("jpg") | ext.toLowerCase().equals("jpeg")) {
					ps.setString(1, dir.toString());
					String[] fileParts = dir.toString().replace(topLevelPhotoDir.toString() + File.separator, "")
							.split(File.separator);
					String tags = "";
					for (int i = 0; i < fileParts.length - 1; i++) {
						tags += fileParts[i].replace(" - ", ": ").replace("-", ".") + ";";
					}
					tags = tags.substring(0, tags.length() - 1);
					ps.setString(2, tags);
					ps.execute();
					ps.clearBatch();
					System.out.println("\tAdded to DB: " + dir + " - " + tags);
					images.put(dir, tags);
					count += 1;
					if (count % 10 == 0) {
						System.out.println("\t\tScanned count: " + count);
					}
				}
			} else {
				System.out.println("\tAlready in DB: " + dir);
				count += 1;
				if (count % 10 == 0) {
					System.out.println("\t\tScanned count: " + count);
				}
			}
		}
	}

	private void searchFlickr() {
		count = 0;
		TreeSet<File> filesFoundOnDisc = new TreeSet<File>(images.keySet());
		for (File file : filesFoundOnDisc) {
			if ((count > 0) && (count % 10 == 0)) {
				System.out.println("\t" + count + " of " + images.size() + " images processed.");
				System.out.println("\tTime running: " + (System.currentTimeMillis() - start) / 1000 + "s");
				System.out.println("\tCurrent upload count: " + notUploaded.size());
				System.out.println("\tCurrent undistinguishable count: " + cannotDistinguish.size());
			}
			if (file.toString().contains(".Picasa3Temp")) {
				System.out.println("\tIgnoring: " + file);
				continue;
			}
			if (cannotDistinguish.contains(file)) {
				System.out.println("\tDatabase shows that the search_count is greater than 1 for " + file);
				continue;
			}
			if (notUploaded.contains(file)) {
				System.out.println("\tDatabase shows that the search_count is 0 so it has been added to the list of uploaded files: " + file);
				continue;
			}
			if (!alreadyUploaded.contains(file)) {
				System.out.println("\tSearching flickr for " + file);
				try {
					PhotosInterface photoInt = flickr.getPhotosInterface();
					SearchParameters searchParams = new SearchParameters();
					searchParams.setMedia("photos");
					searchParams.setText(file.getName() + " " + images.get(file).toLowerCase().replaceAll("[ .,:'@-_=+)(*&^%$£!`]", "").replace(";", " ") + "");
					searchParams.setUserId("47213205@N04");

					PhotoList results = photoInt.search(searchParams, 50, 0);
					// System.out.println(results.size());

					try {
						s = conn.createStatement();
						if (results.size() == 0) {
							notUploaded.add(file);
							s.execute("UPDATE photos SET id=" + id + ", uploaded='false', search_count=0 WHERE file='"
									+ file.toString().replaceAll("'", "''") + "'");
							System.out.println("\t\tScheduled for uploading. No results found.");
						} else if (results.size() == 1) {
							alreadyUploaded.add(file);
							// Update the id number
							Photo p = (Photo) results.get(0);
							int updatedID = Integer.parseInt(p.getTitle().split("\\.")[0]);
							System.out.println("\tPhoto id:" + updatedID);
							s.execute("UPDATE photos SET id=" + updatedID
									+ ", uploaded='true', search_count=1 WHERE file='"
									+ file.toString().replaceAll("'", "''") + "'");

						} else {
							cannotDistinguish.add(file);
							System.out.println("\t\tFound multiple images on flickr for " + file + ": "
									+ results.size());
							// Update the search_count number
							s.execute("UPDATE photos SET search_count=" + results.size() + " WHERE file='"
									+ file.toString().replaceAll("'", "''") + "'");
						}
					} catch (SQLException se) {
						se.printStackTrace();
					}

				} catch (FlickrException fe) {
					fe.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				} catch (SAXException e) {
					e.printStackTrace();
				}
			} else {
				System.out.println("\tDB says " + file + " has already been uploaded.");
			}
			count += 1;
		}
	}

	private void saveResults() {
		System.out.println("Saving results to file.");
		try {
			BufferedWriter out = new BufferedWriter(new FileWriter("flickrUploaderResults.log"));
			out.write("Already Uploaded Files:\n");
			for (File f : alreadyUploaded) {
				out.write(f + "\n");
			}
			out.write("\nReady to upload:\n");
			for (File f : notUploaded) {
				out.write(f + "\n");
			}
			out.write("\nCannot Distinguish:\n");
			for (File f : cannotDistinguish) {
				out.write(f + "\n");
			}
			out.close();
		} catch (IOException ioe) {
			ioe.printStackTrace();
		}
	}

	private void executeUploads() {
		try {
			s = conn.createStatement();
			count = 0;
			for (File f : notUploaded) {
				if ((count > 0) && (count % 10 == 0)) {
					System.out.println("\t" + count + " of " + notUploaded.size() + " images uploaded.");
					System.out.println("\tTime running: " + (System.currentTimeMillis() - start) / 1000 + "s");
				}
				System.out.println("\tUploading: " + f);
				String[] tags = f.toString().replace(topLevelPhotoDir + File.separator, "")
						.replace(File.separator + f.getName(), "").split(File.separator);
				ArrayList<String> tagsArray = new ArrayList<String>();
				for (String tag : tags) {
					tagsArray.add("\"" + tag.replace(" - ", ": ").replace("-", ".") + "\"");
				}
				tagsArray.add("\"Automatically Uploaded\"");
				System.out.println("\t\tTags: " + tagsArray);
				System.out.println("\t\tTitle: " + id + ". " + f.getName());
				// Update the database
				s.execute("UPDATE photos SET id=" + id + ", uploaded='true' WHERE file='"
						+ f.toString().replaceAll("'", "''") + "'");
				// Perform the upload
				up = flickr.getUploader();
				UploadMetaData uploadMetaData = new UploadMetaData();
				uploadMetaData.setTitle(id + ". " + f.getName());
//				System.out.println(uploadMetaData.getTitle());
				uploadMetaData.setTags(tagsArray);
//				System.out.println(uploadMetaData.getTags());
				byte[] data = getBytesFromFile(f);
				up.upload(data, uploadMetaData);

				// Increment the count
				count += 1;
			}
		} catch (SQLException se) {
			se.printStackTrace();
		} catch (IOException ioe) {
			ioe.printStackTrace();
		} catch (FlickrException fe) {
			fe.printStackTrace();
		} catch (SAXException se) {
			se.printStackTrace();
		}
	}

	private void cleanUp() {
		// Close the connection
		try {
			// the shutdown=true attribute shuts down Derby
			DriverManager.getConnection("jdbc:derby:Photos;shutdown=true");
			// To shut down a specific database only, but keep the
			// engine running (for example for connecting to other
			// databases), specify a database in the connection URL:
			// DriverManager.getConnection("jdbc:derby:" + dbName +
			// ";shutdown=true");
		} catch (SQLException se) {
			if (((se.getErrorCode() == 50000) && ("XJ015".equals(se.getSQLState())))) {
				// we got the expected exception
				System.out.println("Derby shut down normally");
				// Note that for single database shutdown, the expected
				// SQL state is "08006", and the error code is 45000.
			} else if (((se.getErrorCode() == 45000) && ("08006".equals(se.getSQLState())))) {
				System.out.println("Derby single connection to db shut down normally");
			} else {
				System.err.println("Derby did not shut down normally");
				se.printStackTrace();
			}
		}
	}

	public static byte[] getBytesFromFile(File file) throws IOException {
		InputStream is = new FileInputStream(file);

		// Get the size of the file
		long length = file.length();

		if (length > Integer.MAX_VALUE) {
			// File is too large
		}

		// Create the byte array to hold the data
		byte[] bytes = new byte[(int) length];

		// Read in the bytes
		int offset = 0;
		int numRead = 0;
		while (offset < bytes.length && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {
			offset += numRead;
		}

		// Ensure all the bytes have been read in
		if (offset < bytes.length) {
			throw new IOException("Could not completely read file " + file.getName());
		}

		// Close the input stream and return bytes
		is.close();
		return bytes;
	}

	public static void main(String[] args) {
		// Get the arguments
		if (args.length != 1) {
			System.err.println("Incorrect arguments.\nUsage: java -jar flickruploader.jar <directoryOfPhotos>");
			System.exit(-1);
		}
		FlickrUploader uploader = new FlickrUploader(args[0]);
		uploader.searchFlickr();
		uploader.saveResults();
		uploader.executeUploads();
		uploader.cleanUp();

	}

}
